
package combat.dudes.server;

import gnu.cajo.invoke.Remote;
import gnu.cajo.utils.ItemServer;

import java.util.ArrayList;

import combat.dudes.DudesConfig;
import combat.dudes.DudesConfig.SharedVars;
import combat.dudes.DudesFunctions;
import combat.dudes.client.ClientApp;
import combat.dudes.protocol.data.DoublePoint;
import combat.dudes.protocol.interfaces.OrderInt;
import combat.dudes.protocol.interfaces.ProjectileInt;
import combat.dudes.protocol.interfaces.TeamInt;
import combat.dudes.protocol.interfaces.VehicleInt;
import combat.dudes.protocol.types.VehicleType;
import combat.dudes.server.classes.Calculation;
import combat.dudes.server.classes.VehicleUpdate;
import combat.dudes.server.protocol.implementation.Chat;
import combat.dudes.server.protocol.implementation.OnboardWeaponSystem;
import combat.dudes.server.protocol.implementation.Order;
import combat.dudes.server.protocol.implementation.OrderMovement;
import combat.dudes.server.protocol.implementation.Scenario;
import combat.dudes.server.protocol.implementation.Simulation;
import combat.dudes.server.protocol.implementation.Team;
import combat.dudes.server.protocol.implementation.User;
import combat.dudes.server.protocol.implementation.Vehicle;


/**
 * The server application of the D.U.D.E.S. Combat project
 * @author Laurens Andriessen
 * @author Jesper Keijzer
 * @author Gerard Krombeen
 * @version 1.0
 */
public class ServerApp {

	/**
	 * The start of the D.U.D.E.S. Combat Management System Server Application
	 * @author Laurens Andriessen
	 * @author Jesper Keijzer
	 * @author Gerard Krombeen
	 * @param args
	 * @return void
	 */
	public static void main(String[] args) {

		// Output the status
		System.out.println(DudesFunctions.Status.getStatus());
		Log("D.U.D.E.S. Combat Server starting");
		Log("Server information:");
		Log("IP: " + DudesFunctions.Network.getMyIPAddress());


		// [debug] Show system information
		/* if(DudesConfig.debugMode)
		 * for(Map.Entry<Object, Object> prop : System.getProperties().entrySet())
		 * System.out.printf("%-30s : %50s%n", prop.getKey(), prop.getValue()); */


		// Set up networking (CAJO bindings)
		try {
			// Configure CAJO remote server, listening on port 28000 (defined in combat.dudes.DudesConfig.java)
			Remote.config(null, DudesConfig.serverPort, null, 0);

			try {
				// Set up static CAJO servers for classes with a single instance
				// (only one version/occurrence of the class)
				ItemServer.bind(Simulation.instance(), "Simulation");
				ItemServer.bind(Scenario.instance(), "Scenario");
				ItemServer.bind(Chat.instance(), "Chat");
			} catch(Exception e) {
				onError("Error! Server already running!", e, true);
			}

			// Set up teams
			Simulation.instance().addTeam(new Team("Spectators")); // Team Spectators
			Simulation.instance().addTeam(new Team("Defense")); // Team Defense
			Simulation.instance().addTeam(new Team("Offense")); // Team Offense
		} catch(Exception e) {
			onError("Error! Server creation failed...", e, true);
		}

		Log("Server running...");


		// -------------------------------------

		// TEST DATA

		// -------------------------------------


		Simulation.instance().startSimulation(); // Start the simulation
		Simulation.instance().setSimSpeed(0.0); // Standard pauze: start with the operator application!

		// Test user 1
		User me = (User) Simulation.instance().userLogOn("Jesper");
		Simulation.instance().switchTeam(me, Simulation.instance().getTeams().get(1)); // to team defense
		Team myTeam = (Team) Simulation.instance().findTeamOfUser(me);

		// Add vehicle
		DoublePoint startPostion = new DoublePoint(30, 30);
		me.setUserVehicle(new Vehicle(VehicleType.CONVOY, "Convoy-Jesper", startPostion, 0, 45, 30, 500, 100, false, new ArrayList<OrderInt>()));
		myTeam.addVehicle(me.getUserVehicle());

		// Add orders
		DoublePoint TargetPosition = new DoublePoint(40, 40);
		me.getUserVehicle().orderMove(1, TargetPosition, 70.0);
		TargetPosition = new DoublePoint(20, 60);
		me.getUserVehicle().orderMove(2, TargetPosition, 45.0);
		TargetPosition = new DoublePoint(90, 4);
		me.getUserVehicle().orderMove(3, TargetPosition, 22.2265);


		// Test user 2
		User secondUser = (User) Simulation.instance().userLogOn("Gerard");
		Simulation.instance().switchTeam(secondUser, Simulation.instance().getTeams().get(2)); // to team offense
		Team secondUserTeam = (Team) Simulation.instance().findTeamOfUser(secondUser);

		// Add vehicle
		DoublePoint secondUserStartPostion = new DoublePoint(108, 107);
		secondUser.setUserVehicle(new Vehicle(VehicleType.FRIGATE, "Frigate-Gerard", secondUserStartPostion, 0, 45, 30, 500, 100, false, new ArrayList<OrderInt>()));
		secondUserTeam.addVehicle(secondUser.getUserVehicle());

		// Add orders
		DoublePoint secondUserTargetPosition = new DoublePoint(125, 90);
		secondUser.getUserVehicle().orderMove(4, secondUserTargetPosition, 100.0);
		secondUser.getUserVehicle().orderAttack(1, secondUserTargetPosition, (OnboardWeaponSystem) secondUser.getUserVehicle().getOnboardSystems().get(3));
		secondUserTargetPosition = new DoublePoint(80, 56);
		secondUser.getUserVehicle().orderMove(5, secondUserTargetPosition, 10.0);
		secondUserTargetPosition = new DoublePoint(6, 60);
		secondUser.getUserVehicle().orderMove(6, secondUserTargetPosition, 55.0);


		// Test user 3
		User thirdUser = (User) Simulation.instance().userLogOn("Laurens");
		Simulation.instance().switchTeam(thirdUser, Simulation.instance().getTeams().get(1));
		Team thirdUserTeam = (Team) Simulation.instance().findTeamOfUser(thirdUser);

		// Add vehicle
		DoublePoint thirdUserStartPostion = new DoublePoint(125, 90);
		thirdUser.setUserVehicle(new Vehicle(VehicleType.FRIGATE, "Frigate-Laurens", thirdUserStartPostion, 0, 0, 0, 500, 100, false, new ArrayList<OrderInt>()));

		// Add orders
		thirdUserTeam.addVehicle(thirdUser.getUserVehicle());
		thirdUser.setUserVehicle(thirdUser.getUserVehicle());
		thirdUser.getUserVehicle().orderAttack(2, Simulation.instance().getTeams().get(2).getProjectiles().get(0), ((OnboardWeaponSystem) thirdUser.getUserVehicle().getOnboardSystems().get(1)));
		// System.out.println(thirdUser.getUserVehicle().getPosition().toString());


		// Add two 'open' ships (not locked, nor set to any user), from which the client can choose
		Vehicle veh = new Vehicle(VehicleType.SUBMARINE, "Sub-Marine 1", new DoublePoint(75, 75), -1, 0, 30, 320, 100, false, new ArrayList<OrderInt>());
		thirdUserTeam.addVehicle(veh);
		Vehicle veh2 = new Vehicle(VehicleType.FRIGATE, "Frigate Tim", new DoublePoint(25, 25), -1, 0, 50, 500, 100, false, new ArrayList<OrderInt>());
		secondUserTeam.addVehicle(veh2);


		// -------------------------------------

		// END OF TEST DATA

		// -------------------------------------


		// Endless while-loop
		while(true) {
			try {
				Thread.sleep(DudesConfig.SharedVars.getRefreshTime());

				for(TeamInt team : Simulation.instance().getTeams()) {

					for(VehicleInt vehicleI : team.getVehicles()) {

						if(vehicleI.getHealth() > 0) {
							VehicleUpdate.UpdateVehicle(vehicleI);
							for(TeamInt teamI : Simulation.instance().getTeams())
								for(VehicleInt vehicleII : teamI.getVehicles())
									if(!vehicleI.equals(vehicleII)
											&& Calculation.MainCalc.distance(vehicleI.getPosition(), vehicleII.getPosition()) < DudesConfig.SharedVars.getHitRange(vehicleI.getType())) {
										vehicleI.setHealth(vehicleI.getHealth() - Calculation.DamageCalc.hitTable.getDamage(vehicleI.getVehicleType(), vehicleII.getVehicleType()).getTaskObject1());
										vehicleII.setHealth(vehicleII.getHealth() - Calculation.DamageCalc.hitTable.getDamage(vehicleI.getVehicleType(), vehicleII.getVehicleType()).getTaskObject2());
										ServerApp.Log("Vehicle '" + vehicleI.getName() + "' hits Vehicle '" + vehicleII.getName() + "'");
										if(vehicleI.getHealth() <= 0)
											Log("Vehicle '" + vehicleI.getName() + "' was defeated");
										if(vehicleII.getHealth() <= 0)
											Log("Vehicle '" + vehicleII.getName() + "' was defeated");
									}

							Vehicle vehicle = (Vehicle) vehicleI;
							ArrayList<OrderInt> orderRemoveList = new ArrayList<OrderInt>();
							boolean moveLock = false;
							for(OrderInt order : vehicle.getOrders())
								switch(order.getType()) {
									case UNKNOWN:
										// TODO handle unknown orders
										break;
									case MOV:
										// Check moveLock so only the first movement will be handled
										if(!moveLock) {
											moveLock = true;

											// Typecast order to movement
											OrderMovement moveorder = (OrderMovement) order;

											// Cancel order if target arrived (deviate margin)
											if(Math.abs(Calculation.MainCalc.distance(vehicle.getPosition(), order.getTarget())) < DudesConfig.SharedVars.getArriveRange()) {
												vehicle.setSpeed(0);
												Log("Move order ID " + moveorder.getOrderID() + " of " + vehicle.getName() + " completed");
												vehicle.orderCancel(moveorder);
											} else {
												// System.out.println("Error margin: " + (ServerApp.SharedVars.getRefreshTime()/600));
												// System.out.println("Distance: " + Math.abs(Calculation.MainCalc.distance(vehicle.getPosition(), order.getTarget())) + "\n");

												// Adjust orderSpeed
												double max = Calculation.MainCalc.getMaxSpeed(vehicle.getVehicleType(), vehicle.getHealth());

												if(Math.abs(moveorder.getSpeed()) > max)
													moveorder.setSpeed(max);
												vehicle.setSpeed(moveorder.getSpeed());

												// Set the right direction
												vehicle.setDirection(Calculation.MainCalc.nextDirection(
														vehicle.getDirection(),
														vehicle.getPosition(),
														moveorder.getTarget(),
														vehicle.getVehicleType(),
														vehicle.getHealth()));
											}

											// Add order to removeList if canceled
											if(moveorder.getOrderID() == -1) {
												orderRemoveList.add(order);
												moveLock = false;
											}

										}
										break;
									case ATK:
										// Directly handled within orderAttack() @ combat.dudes.server.protocol.implementation.Vehicle.java
										break;
									default:
										break;
								}


							// Move the vehicle in right direction
							vehicle.setFuel(vehicle.getFuel() - (2.0 * (Math.abs((Calculation.MainCalc.distance(vehicle.getPosition(),
											Calculation.MainCalc.nextPosition(vehicle.getPosition(), vehicle.getSpeed(), vehicle.getDirection())))))));
							if(vehicle.getFuel() > 0.0)
								vehicle.setPosition(Calculation.MainCalc.nextPosition(vehicle.getPosition(), vehicle.getSpeed(), vehicle.getDirection()));


							// Show vehicle movement - start
							/* System.out.print(new Date() + " | Name: " + vehicle.getName() + " | speed: " + vehicle.getSpeed() + " | direction: " + vehicle.getDirection() + " | position: "
							 * + vehicle.getPosition());
							 * for (OrderInt order : vehicle.getOrders()) {
							 * if (order.getType() == OrderType.MOV) {
							 * System.out.print(" | Distance: " + Math.abs(Calculation.MainCalc.distance(vehicle.getPosition(), vehicle.getOrders().get(0).getTarget())) + " | checkMarge: "
							 * + (100.0 / DudesConfig.SharedVars.getRefreshTime()));
							 * break;
							 * }
							 * }
							 * System.out.print("\n"); */
							// show vehicle movement - end
							/* for (PhysicalObject po : Simulation.instance().update(myTeam))
							 * System.out.println("First pla0yer sees: " + po.getName() + " postion: " + po.getPosition() + " distance:" + Calculation.MainCalc.distance(myVehicle.getPosition(), po));
							 * System.out.print("\n"); */


							// Remove canceled orders
							for(OrderInt order : orderRemoveList)
								vehicle.orderRemove((Order) order);

						} else vehicleI.setSpeed(0);
					}


					ArrayList<ProjectileInt> projectileRemoveList = new ArrayList<ProjectileInt>();
					for(ProjectileInt projectile : team.getProjectiles()) {
						// Check if projectile must be moved or exploded
						if((Math.abs(Calculation.MainCalc.distance(projectile.getPosition(), projectile.getTarget())) > (DudesConfig.SharedVars.getArriveRange()))
								&& (Math.abs(Calculation.MainCalc.distance(projectile.getSource(), projectile.getPosition())) < projectile.getMaxDistance())) {
							projectile.setDirection(Calculation.MainCalc.direction(projectile.getPosition(), projectile.getTarget()));
							projectile.setPosition(Calculation.MainCalc.nextPosition(projectile.getPosition(), projectile.getSpeed(), projectile.getDirection()));
							// System.out.println("Projectile " + projectile.getName() + "set to " + projectile.getPosition());

						} else { // Add to removelist
							projectileRemoveList.add(projectile);
							Log("Projectile '" + projectile.getName() + "' exploded on " + DudesFunctions.DoublePointToString(projectile.getPosition()));

							for(TeamInt selectedTeam : Simulation.instance().getTeams()) {
								for(VehicleInt selectedVehicle : selectedTeam.getVehicles())
									if(Calculation.MainCalc.distance(selectedVehicle.getPosition(), projectile.getPosition()) < SharedVars.getHitRange(projectile.getType())) {
										ServerApp.Log("Projectile '" + projectile.getName() + "' hits '" + selectedVehicle.getName() + "'");
										selectedVehicle.setHealth(selectedVehicle.getHealth()
												- Calculation.DamageCalc.hitTable.getDamage(projectile.getProjectileType(), selectedVehicle.getVehicleType()).getTaskObject2());
										Log(selectedVehicle.getName() + " health: " + selectedVehicle.getHealth());
									}
								/* else
								 * ServerApp.Log("projectile missed " + selectedVehicle.getName() + " on "
								 * + Calculation.MainCalc.distance(selectedVehicle.getPosition(), projectile.getPosition()) + " distance");// TODO handle damage */

								for(ProjectileInt selectedProjectile : selectedTeam.getProjectiles())
									if(!projectile.equals(selectedProjectile)
											&& Calculation.MainCalc.distance(selectedProjectile.getPosition(), projectile.getPosition()) < SharedVars.getHitRange(projectile.getType())) {
										ServerApp.Log("projectile '" + projectile.getName() + "' hits '" + selectedProjectile.getName() + "'");
										if(Calculation.DamageCalc.hitTable.getDamage(projectile.getProjectileType(), selectedProjectile.getProjectileType()).getTaskObject2() == 100) {
											// set MaxDistance to 0 so it will explode next loop
											selectedProjectile.setMaxDistance(0);
											Log("Projectile '" + projectile.getName() + "' destroyed '" + selectedProjectile.getName() + "'");
										}

									}
								/* else
								 * ServerApp.Log("projectile missed " + selectedProjectile.getName() + " on "
								 * + Calculation.MainCalc.distance(selectedProjectile.getPosition(), projectile.getPosition()) + " distance");// TODO handle damage */
							}
						}
					}

					// Remove canceled projectiles
					for(ProjectileInt projectile : projectileRemoveList)
						team.removeProjectile(projectile);

				}


			} catch(InterruptedException e) {
				ClientApp.onError("", e, true);
			}
		}
	}
	/**
	 * Alias for the setStatus()-method in DudesConfig.java
	 * @author Laurens Andriessen
	 */
	public static void Log(String status) {
		DudesFunctions.Status.setStatus(status);
	}

	/**
	 * Alias for the onError()-method in DudesConfig.java
	 * @author Laurens Andriessen
	 */
	public static void onError(String status, Exception e, Boolean doExit) {
		DudesFunctions.Status.onError(status, e, doExit);
	}
}
